package wechat

import (
	"context"
	"errors"
	"fmt"
	"log"
	"net/http"

	"github.com/gin-gonic/gin"

	"gin-api-server/core_library/global"

	"github.com/duke-git/lancet/v2/fileutil"
	"github.com/go-pay/gopay"
	"github.com/go-pay/gopay/wechat/v3"
	"github.com/gookit/goutil/fsutil"
)

type Pay struct {
	debug             bool
	miniappId         string
	mpappId           string
	appAppid          string
	mchid             string
	secretV2          string
	secretV3          string
	wechatpaySerialNo string
	wechatpayCertKey  string
	wechatpayCertPem  string
	privateKey        string
}

func (p Pay) GetClient() *wechat.ClientV3 {
	client, err := wechat.NewClientV3(p.mchid, p.wechatpaySerialNo, p.secretV3, p.privateKey)
	if err != nil {
		return nil
	}
	err = client.AutoVerifySign()
	if err != nil {
		return nil
	}
	return client
}

type MiniAppPayResult struct {
	TimeStamp string `json:"timeStamp"`
	NonceStr  string `json:"nonceStr"`
	Package   string `json:"package"`
	SignType  string `json:"signType"`
	PaySign   string `json:"paySign"`
}

func (p Pay) MiniAppPay(goodsDesc, outTradeNo, notifyUrl, openid string, amount float64) (MiniAppPayResult, error) {

	client, err := wechat.NewClientV3(p.mchid, p.wechatpaySerialNo, p.secretV3, p.privateKey)
	if err != nil {
		return MiniAppPayResult{}, err
	}
	err = client.AutoVerifySign()
	if err != nil {
		return MiniAppPayResult{}, err
	}

	if p.debug {
		client.DebugSwitch = gopay.DebugOn
	}
	bm := make(gopay.BodyMap)
	bm.Set("appid", p.miniappId).
		Set("mchid", p.mchid).
		Set("description", goodsDesc).Set("out_trade_no", outTradeNo).Set("notify_url", notifyUrl).
		SetBodyMap("amount", func(bm gopay.BodyMap) {
			bm.Set("total", int64(amount*100))
			bm.Set("currency", "CNY")
		}).
		SetBodyMap("payer", func(bm gopay.BodyMap) {
			bm.Set("openid", openid)
		})

	wxRsp, err := client.V3TransactionJsapi(context.Background(), bm)
	if err != nil {
		return MiniAppPayResult{}, err
	}

	if wxRsp.Code == 0 {
		applet, _ := client.PaySignOfApplet(p.miniappId, wxRsp.Response.PrepayId)
		return MiniAppPayResult{
			TimeStamp: applet.TimeStamp,
			NonceStr:  applet.NonceStr,
			Package:   applet.Package,
			SignType:  applet.SignType,
			PaySign:   applet.PaySign,
		}, nil
	} else {
		return MiniAppPayResult{}, fmt.Errorf(wxRsp.Error)
	}

}

type JsApiPauResult struct {
	AppId     string `json:"app_id"`
	TimeStamp string `json:"time_stamp"`
	NonceStr  string `json:"nonce_str"`
	Package   string `json:"package"`
	SignType  string `json:"sign_type"`
	PaySign   string `json:"pay_sign"`
}

func (p Pay) JsApiPay(goodsDesc, outTradeNo, notifyUrl, openid string, amount float64) (JsApiPauResult, error) {

	client, err := wechat.NewClientV3(p.mchid, p.wechatpaySerialNo, p.secretV3, p.privateKey)
	if err != nil {
		return JsApiPauResult{}, err
	}
	err = client.AutoVerifySign()
	if err != nil {
		return JsApiPauResult{}, err
	}

	if p.debug {
		client.DebugSwitch = gopay.DebugOn
	}

	bm := make(gopay.BodyMap)
	bm.
		Set("appid", p.mpappId).
		Set("mchid", p.mchid).
		Set("description", goodsDesc).
		Set("out_trade_no", outTradeNo).
		Set("notify_url", notifyUrl).
		SetBodyMap("amount", func(b gopay.BodyMap) {
			b.Set("total", amount)
		}).
		SetBodyMap("payer", func(b gopay.BodyMap) {
			b.Set("openid", openid)
		})

	wxRsp, err := client.V3TransactionJsapi(context.Background(), bm)
	if err != nil {
		return JsApiPauResult{}, err
	}

	if wxRsp.Code == 0 {
		applet, _ := client.PaySignOfJSAPI(p.mpappId, wxRsp.Response.PrepayId)

		return JsApiPauResult{
			AppId:     applet.AppId,
			TimeStamp: applet.TimeStamp,
			NonceStr:  applet.NonceStr,
			Package:   applet.Package,
			SignType:  applet.SignType,
			PaySign:   applet.PaySign,
		}, nil
	} else {
		return JsApiPauResult{}, fmt.Errorf(wxRsp.Error)
	}

}

type NativePayResult struct {
	CodeUrl string `json:"code_url"`
}

func (p Pay) NativePay(orderSn, goodsDesc, notifyUrl string, price float64) (NativePayResult, error) {
	client, err := wechat.NewClientV3(p.mchid, p.wechatpaySerialNo, p.secretV3, p.privateKey)
	if err != nil {
		return NativePayResult{}, err
	}
	err = client.AutoVerifySign()
	if err != nil {
		return NativePayResult{}, err
	}

	if p.debug {
		client.DebugSwitch = gopay.DebugOn
	}
	amount := int64(price * 100)

	bm := make(gopay.BodyMap)
	bm.Set("appid", p.mpappId).
		Set("mchid", p.mchid).
		Set("description", goodsDesc).
		Set("out_trade_no", orderSn).
		Set("notify_url", notifyUrl).
		SetBodyMap("amount", func(b gopay.BodyMap) {
			b.Set("total", amount)
		})

	wxRsp, err := client.V3TransactionNative(context.Background(), bm)
	if err != nil {
		return NativePayResult{}, err
	}

	if wxRsp.Code == 0 {
		return NativePayResult{CodeUrl: wxRsp.Response.CodeUrl}, nil
	} else {
		return NativePayResult{}, fmt.Errorf(wxRsp.Error)
	}

}

func (p Pay) AppPay(goodsDesc, outTradeNo, notifyUrl string, amount float64) (*wechat.AppPayParams, error) {
	client, err := wechat.NewClientV3(p.mchid, p.wechatpaySerialNo, p.secretV3, p.privateKey)
	if err != nil {
		return nil, err
	}
	err = client.AutoVerifySign()
	if err != nil {
		return nil, err
	}

	if p.debug {
		client.DebugSwitch = gopay.DebugOn
	}

	bm := make(gopay.BodyMap)
	bm.
		Set("appid", p.appAppid).
		Set("mchid", p.mchid).
		Set("description", goodsDesc).
		Set("out_trade_no", outTradeNo).
		Set("notify_url", notifyUrl).
		SetBodyMap("amount", func(b gopay.BodyMap) {
			b.Set("total", int64(amount*100))
		})

	wxRsp, err := client.V3TransactionApp(context.Background(), bm)
	if err != nil {
		return nil, err
	}

	if wxRsp.Code == 0 {
		applet, _ := client.PaySignOfApp(p.appAppid, wxRsp.Response.PrepayId)
		return applet, nil
	} else {
		return nil, fmt.Errorf(wxRsp.Error)
	}

}

func (p Pay) Refund(outTradeNo, outRefundNo, notifyUrl string, amount float64, orderprice float64) error {
	client, err := wechat.NewClientV3(p.mchid, p.wechatpaySerialNo, p.secretV3, p.privateKey)
	if err != nil {
		return err
	}
	err = client.AutoVerifySign()
	if err != nil {
		return err
	}

	if p.debug {
		client.DebugSwitch = gopay.DebugOn
	}

	bm := make(gopay.BodyMap)
	bm.
		Set("out_trade_no", outTradeNo).
		Set("out_refund_no", outRefundNo).
		Set("notify_url", notifyUrl).
		SetBodyMap("amount", func(b gopay.BodyMap) {
			b.Set("refund", amount*100)
			b.Set("total", orderprice*100)
			b.Set("currency", "CNY")
		})

	refundRet, err := client.V3Refund(context.Background(), bm)
	if err != nil {
		return err
	}
	if refundRet.Code == 0 {
		return nil
	} else {
		return errors.New(refundRet.Error)
	}
}

func (p Pay) Notify(ctx *gin.Context, cb func(outTradeNo string) error) error {
	client := p.GetClient()
	notifyReq, err := wechat.V3ParseNotify(ctx.Request)
	if err != nil {
		return err
	}
	wxPublicKey := client.WxPublicKey()
	err = notifyReq.VerifySignByPK(wxPublicKey)
	if err != nil {
		return err
	}
	result, err := notifyReq.DecryptCipherText(global.Config.Wechat.Pay.SecretV3)
	if err != nil {
		return err
	}
	if result.TradeState == "SUCCESS" {
		err := cb(result.OutTradeNo)
		if err == nil {
			ctx.JSON(http.StatusOK, &wechat.V3NotifyRsp{Code: gopay.SUCCESS, Message: "成功"})
		}
	} else {
		log.Println("微信支付失败", result)
	}
	return nil
}

func NewWechatPay(debug bool) *Pay {

	wechatConfig := global.Config.Wechat
	miniappId := wechatConfig.MiniApp.Appid
	mpappId := wechatConfig.Mp.Appid
	mchid := wechatConfig.Pay.MchId
	secretV2 := ""
	secretV3 := wechatConfig.Pay.SecretV3
	wechatpaySerialNo := wechatConfig.Pay.SerialNo
	wechatpayCertKey := global.RootPath + wechatConfig.Pay.CertKeyPath
	wechatpayCertPem := global.RootPath + wechatConfig.Pay.CertPath
	var privateKey string
	if fsutil.FileExist(wechatpayCertKey) {
		_privateKey, err := fileutil.ReadFileToString(wechatpayCertKey)
		if err != nil {
			panic("read wechatpayCertKey error")
		}
		privateKey = _privateKey
	}

	return &Pay{
		debug:             debug,
		miniappId:         miniappId,
		mpappId:           mpappId,
		mchid:             mchid,
		appAppid:          wechatConfig.App.Appid,
		secretV2:          secretV2,
		secretV3:          secretV3,
		wechatpaySerialNo: wechatpaySerialNo,
		wechatpayCertKey:  wechatpayCertKey,
		wechatpayCertPem:  wechatpayCertPem,
		privateKey:        privateKey,
	}
}
